import { observable, action, computed, toJS } from 'mobx';
import { EventEmitter } from 'events';

export interface TabData {
  pathname: string;
  search: string;
  hash: string;
}

const EMPTY_TAB_DATA = {
  pathname: '',
  search: '',
  hash: '',
};

export class UIState extends EventEmitter {
  constructor() {
    super();
    this.setMaxListeners(15);
  }
  @observable.shallow tabs: TabData[] = [];
  @observable.struct fallbackTab = {
    pathname: '/',
    search: '',
    hash: '',
  };
  @observable _activeIndex = -1;

  @computed get isTabClosable() {
    return this.tabs.length > 1
      ? true
      : this.tabs.length === 1 &&
          !this.isTabEqual(this.tabs[0], this.fallbackTab);
  }

  @computed get activeIndex() {
    return this._activeIndex;
  }
  set activeIndex(val: number) {
    const prev = this._activeIndex;
    this._activeIndex = val;
    this.emit(this.Events.SET_TAB_ACTIVE_INDEX, prev, val);
  }
  Events = {
    BEFORE_TAB_UPDATE: 'before_tab_update',
    AFTER_TAB_UPDATE: 'after_tab_update',
    BEFORE_TAB_ADD: 'before_tab_add',
    AFTER_TAB_ADD: 'after_tab_add',
    BEFORE_TAB_REMOVE: 'before_tab_remove',
    AFTER_TAB_REMOVE: 'after_tab_remove',
    SET_TAB_ACTIVE_INDEX: 'tab_index_change',
    TAB_GUARD_PASS: 'tab_guard_pass',
    TAB_GUARD_FAIL: 'tab_guard_fail',
  };
  @computed get currentTab() {
    if (this.activeIndex === -1) {
      return null;
    }
    return this.tabs[this.activeIndex];
  }

  @computed get currentTabUrl() {
    if (this.currentTab === null) {
      return '';
    }
    return this.getTabUrl(this.currentTab);
  }

  @action
  setActiveTab(tab: TabData | null) {
    if (tab == null) {
      this.activeIndex = -1;
      return false;
    }
    const updatedTabIndex = this.updateTab(tab);
    if (updatedTabIndex !== -1) {
      this.activeIndex = updatedTabIndex;
    } else {
      this.activeIndex = this.addTab(tab);
    }
    return true;
  }

  @action
  async setActiveTabWithGuard(tab: TabData, guard: () => Promise<void>) {
    try {
      await guard();
      this.emit(this.Events.TAB_GUARD_PASS, tab);
      this.setActiveTab(tab);
    } catch (e) {
      this.emit(this.Events.TAB_GUARD_FAIL, tab);
    }
  }

  @action
  closeTab(tab?: TabData) {
    const toCloseIndex = tab == null ? this.activeIndex : this.getTabIndex(tab);
    if (toCloseIndex === -1) {
      return false;
    }
    const removedTab = [this.tabs[toCloseIndex]];

    this.emit(this.Events.BEFORE_TAB_REMOVE, removedTab);
    this.tabs.splice(toCloseIndex, 1);
    const nextTabIndex =
      toCloseIndex >= this.tabs.length
        ? toCloseIndex - 1
        : toCloseIndex < this.activeIndex
        ? this.activeIndex - 1
        : this.activeIndex;
    // tabs 内容变化时，需要至少触发一次 activeIndex 的设置
    this.activeIndex = nextTabIndex;
    this.emit(this.Events.AFTER_TAB_REMOVE, removedTab);
    return true;
  }
  @action
  closeOtherTabs(tab: TabData) {
    const targetTabIndex = this.getTabIndex(tab);
    if (targetTabIndex === -1) {
      return false;
    }
    const removedTab = this.tabs.slice();
    removedTab.splice(targetTabIndex, 1);
    this.emit(this.Events.BEFORE_TAB_REMOVE, removedTab);
    const targetTab = this.tabs[targetTabIndex];
    this.tabs = [targetTab];
    this.activeIndex = 0;
    this.emit(this.Events.AFTER_TAB_REMOVE, removedTab);
    return true;
  }
  @action
  closeAll() {
    const removedTab = this.tabs.slice();
    this.emit(this.Events.BEFORE_TAB_REMOVE, removedTab);
    this.tabs = [];
    this.activeIndex = -1;
    this.emit(this.Events.AFTER_TAB_REMOVE, removedTab);
    return true;
  }
  @action
  addTab(tab: TabData, index?: number) {
    const targetTabIndex = this.getTabIndex(tab);
    if (targetTabIndex === -1) {
      const addedIndex = index == null ? this.tabs.length : index;
      this.emit(this.Events.BEFORE_TAB_ADD, addedIndex, tab);
      this.tabs.splice(addedIndex, 0, tab);
      this.emit(this.Events.AFTER_TAB_ADD, addedIndex, tab);
      return addedIndex;
    }
    return -1;
  }
  @action
  updateTab(tab: TabData) {
    const targetTabIndex = this.getTabIndex(tab);
    if (targetTabIndex !== -1) {
      const prev = toJS(this.tabs[targetTabIndex]);
      this.emit(this.Events.BEFORE_TAB_UPDATE, prev, tab);
      this.tabs[targetTabIndex] = {
        ...this.tabs[targetTabIndex],
        ...tab,
      };
      this.emit(this.Events.AFTER_TAB_UPDATE, prev, this.tabs[targetTabIndex]);
      return targetTabIndex;
    }
    return -1;
  }

  getTabIndex(tab?: Partial<TabData>) {
    if (tab == null) {
      return this.activeIndex;
    }
    return this.tabs.findIndex((storedTab) => {
      return this.isTabEqual(storedTab, {
        ...EMPTY_TAB_DATA,
        ...tab,
      });
    });
  }

  getTab(tab?: Partial<TabData>) {
    if (tab == null) {
      return this.currentTab;
    }
    const tabIndex = this.getTabIndex(tab);
    if (tabIndex === -1) {
      return null;
    }
    return this.tabs[tabIndex];
  }

  getTabUrl(tab: Partial<TabData>) {
    const tabIndex = this.getTabIndex(tab);
    const targetTab = tabIndex === -1 ? tab : this.tabs[tabIndex];
    return this.tabToUrl(targetTab);
  }
  tabToUrl(tab: Partial<TabData>) {
    return [tab.pathname, tab.search, tab.hash]
      .filter((item) => item != null)
      .join('');
  }
  toTabUID(tab: TabData) {
    return tab.pathname;
  }

  isTabEqual(tab1: TabData, tab2: TabData) {
    return this.toTabUID(tab1) === this.toTabUID(tab2);
  }
}
